#include <GL/glew.h>
#include "SFML/Graphics.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/vector_access.hpp>
#include <iostream>

#include "Light.hpp"

#ifndef __RENDER_BUFFER_HPP__
#define __RENDER_BUFFER_HPP__

using namespace glm;
using namespace std;

/** class for managing reflection with extra rendering buffer */
class ReflectBuffer {
	private:
		/** openGL identifier for depth buffer */
		GLuint depthBuffer;
		/** pointer to openGL identifier for reflect framebuffer */
		GLuint* reflectBuffer;
		/** openGL texture identifier to which the buffer will be rendered */
		GLuint renderToTexture;
	public:
		/** constructor : generate buffers for rendering and texture */
		ReflectBuffer();

		~ReflectBuffer() {
			glDeleteRenderbuffers(1,reflectBuffer);
		}

		GLuint getRenderToTexture() { return renderToTexture;};

		/** setting the render buffer, each render after this call
		  * will be render into reflect framebuffer */
		void set();

		/** unsetting the render buffer. the normal framebugger (screen)
		  * become the current framebuffer again */
		void unset();
};


class ShadowBuffer {
	private:
		GLuint shadowMapTexture;
		GLuint* shadowFrameBuffer;
		GLfloat* shadowProjectionMatrix;

		float m_zNear;
		float m_zFar;
	public:
		GLuint getShadowMapTexture() { return shadowMapTexture;};
		GLfloat* getProjectionMatrix() { return shadowProjectionMatrix;};

		void setZ(float near_, float far_) {
			m_zNear = near_;
			m_zFar = far_;
		};		

		ShadowBuffer(){
			shadowProjectionMatrix = new GLfloat[16];
	
			m_zNear = 1.0;
			m_zFar = 200.0;

			// Shadow map
			shadowMapTexture = 0;
			glGenTextures(1,&shadowMapTexture);
			glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
    		// GL_LINEAR does not make sense for depth texture. However, next tutorial shows usage of GL_LINEAR and PCF
    		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	
    		// Remove artefact on the edges of the shadowmap
    		glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    		glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
	
			glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,512,512,0,GL_DEPTH_COMPONENT,GL_UNSIGNED_BYTE,NULL);
			glBindTexture(GL_TEXTURE_2D,0);

			shadowFrameBuffer = new GLuint[1];	
			glGenFramebuffers(1, shadowFrameBuffer);
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shadowFrameBuffer[0]);

    		glDrawBuffer(GL_NONE);
    		glReadBuffer(GL_NONE);
			glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMapTexture, 0);

			glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);	
		};
		void set() {
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, shadowFrameBuffer[0]);
			glEnable(GL_DEPTH_TEST);
	    	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
			// building shadow map
			glViewport(0,0,512,512);
			glClearColor(0.0,0.0,0.0,0.0);
			// cleaning buffer before rendering
			glClear(GL_DEPTH_BUFFER_BIT);
	
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			gluPerspective(90.0,1.0,m_zNear, m_zFar);
			vec3 upV(0.0,0.0,1.0);
			glm::vec4 lightPosition = Light::mainLight->getPosition();
			gluLookAt(	lightPosition.x,
						lightPosition.y,
						lightPosition.z,
						0.0,0.0,0.0,upV.x, upV.y, upV.z);

			//glGetFloatv(GL_MODELVIEW_MATRIX, shadowProjectionMatrix);
			//cout << "DEBUG : MODELVIEW" << endl;
			//printProjectionMatrix();

			glGetFloatv(GL_PROJECTION_MATRIX, shadowProjectionMatrix);
		
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			glDepthMask(GL_TRUE);
		}
		void unset() {
			glEnable(GL_TEXTURE_2D);


			glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);

			glActiveTexture(GL_TEXTURE7);
			glMatrixMode(GL_TEXTURE);

			/*const GLdouble bias[16] = {
        		0.5, 0.0, 0.0, 0.0,
        		0.0, 0.5, 0.0, 0.0,
        		0.0, 0.0, 0.5, 0.0,
    			0.5, 0.5, 0.5, 1.0};*/
	
			// concatating all matrice into one.
		
			glActiveTexture(GL_TEXTURE0);
			glMatrixMode(GL_MODELVIEW);
	
			glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
			glGenerateMipmap(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, 0);//shadowMapTexture);
			/*glBindTexture(GL_TEXTURE_2D, shadowMapView);
			glGenerateMipmap(GL_TEXTURE_2D);*/

	    	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
		};

		void printProjectionMatrix() {
			for (int i = 0; i < 4; i++) {
				for (int j = 0; j < 4; j++) {
					cout << "| " << shadowProjectionMatrix[4*i+j] << " |";
				};
				cout << endl;
			};
		};

};
#endif
